/*
 * 
 * This class defines the protocol for the packets being transmitted over the network. 
 * All the names are self explanatory. 
 */

package runner;

import java.net.InetAddress;
import java.util.Hashtable;

import database.Append;

import server.MultiThreadedServer;

public class Message {

	public static final int MESSAGE_PARTS = 8;
	public static final int MESSAGE_EXCEPTION = 0;
	public static final boolean PRINT_MESSAGE_YES_NO = true;

	private String code;
	private String source;
	private String sourceIP;
	private int sourcePort;
	private String destinationIP;
	private int destinationPort;
	private String destination;
	private String key;
	private String value;
	private long timestamp;
	private int TTL;
	private String msg_machine;

	public void setSourceIP(String iPAddress) {
		this.sourceIP = iPAddress;
	}

	private void setSourcePort(int port) {
		this.sourcePort = port;
	}

	public String getSourceIP() {
		return this.sourceIP;
	}

	public int getSourcePort() {
		return this.sourcePort;
	}

	private void setDestinationIP(String iPAddress) {
		this.destinationIP = iPAddress;
	}

	private void setDestinationPort(int port) {
		this.destinationPort = port;
	}

	public String getDestinationIP() {
		return this.destinationIP;
	}

	public int getDestinationPort() {
		return this.destinationPort;
	}

	public String getCode() {
		return code;
	}

	private String getSource() {
		return source;
	}

	private String getDestination() {
		return destination;
	}

	private String getKey() {
		return key;
	}

	private String getValue() {
		return value;
	}

	private long getTimestamp() {
		return timestamp;
	}

	public int getTTL() {
		return TTL;
	}

	private String getMsg_machine() {
		return msg_machine;
	}

	public void setCode(String code) {
		this.code = code;
	}

	private void setSource(String source) {
		if (source.split("-").length != 2) {
			return;
		}
		setSourceIP(source.split("-")[0]);
		setSourcePort(Integer.parseInt(source.split("-")[1]));
		this.source = source;
	}

	public void setDestination(String destination) {
		this.destination = destination;
	}

	private void setKey(String key) {
		this.key = key;
	}

	private void setValue(String value) {
		this.value = value;
	}

	private void setTimestamp(long timestamp) {
		this.timestamp = timestamp;
	}

	public void setTTL(int tTL) {
		TTL = tTL;
	}

	private void setMsg_machine(String msg_machine) {
		this.msg_machine = msg_machine;
	}

	public void message_print(String str) {
		if (PRINT_MESSAGE_YES_NO) {
			System.out.println("------> Message: " + str);
		}
		return;
	}

	public Message(String inputMessage) {

		// MultiThreadedServer.print(inputMessage);

		String[] msg = inputMessage.split(":");

		int posStart = 1;

		if (msg[0].equalsIgnoreCase("insert")
				|| msg[0].equalsIgnoreCase("machineInsert")
				|| msg[0].equalsIgnoreCase("connectToMe")) {
			posStart = 0;
		}

		if (msg.length < MESSAGE_PARTS + posStart) {
			MultiThreadedServer
					.print("Error: Incorrect format of the message received. Length is: "
							+ msg.length + ", and max-len is: " + MESSAGE_PARTS);

			MultiThreadedServer.print("Message is: " + inputMessage); 
			// System.exit(MESSAGE_EXCEPTION);
		} else {
			setCode(msg[posStart]);
			setSource(msg[posStart + 1]);
			setDestination(msg[posStart + 2]);
			setKey(msg[posStart + 3]);
			setValue(msg[posStart + 4]);
			setTimestamp(Long.parseLong(msg[posStart + 5]));
			setTTL(Integer.parseInt(msg[posStart + 6]));
			setMsg_machine(msg[posStart + 7]);
		}
	}

	public String createConnectionNewMachineMessage(String destination) {
		String str = "connectToMe:" + Message.getMyIP() + "-20000:" + destination  
				+ ":blank_key:blank_value:" + System.currentTimeMillis() + ":"
				+ 1 + ":blank_message";
		return str;
	}

	public int calculateMinArray(int[] array) {
		int min = Integer.MAX_VALUE;
		for (int i = 0; i < array.length; i++) {
			if (array[i] < min) {
				min = array[i];
			}
		}
		return min;
	}

	public int calculateMinPos(int[] array) {
		int min = Integer.MAX_VALUE;
		int pos = -1;
		for (int i = 0; i < array.length; i++) {
			if (array[i] < min) {
				min = array[i];
				pos = i;
			}
		}
		return pos;
	}

	public void showMessage() {
		if (!getCode().equalsIgnoreCase("ping")) {
			// message_print("formMessageNow() + "----------->"
			// + getCode());
		}
	}

	public String analyzeMessage(Hashtable<Integer, String> hashtable) {

//		showMessage();

		if (getCode().equalsIgnoreCase("insert")) {
			setTTL(getTTL() - 1);
			setMsg_machine(getMsg_machine() + hashtable.size() + ",");
		} else if (getCode().equalsIgnoreCase("delete")) {
			setTTL(getTTL() - 1);
			if (hashtable.containsKey(getKey())) {
				setDestination(getMyIP());
				setValue("noValue");
				setTimestamp(System.currentTimeMillis() - getTimestamp());
				message_print("Found the key|value pair @" + getMyIP()
						+ "|Now returning");
			}
			setMsg_machine(getMsg_machine() + ", " + getMyIP());
		} else if (getCode().equalsIgnoreCase("search")) {
			setTTL(getTTL() - 1);
			if (hashtable.containsKey(getKey())) {
				setDestination(getMyIP());
				setValue(hashtable.get(getKey()));
				setTimestamp(System.currentTimeMillis() - getTimestamp());
				message_print("Found the key|value pair @" + getMyIP()
						+ "|Now returning");
			}
			setMsg_machine(getMsg_machine() + ", " + getMyIP());
		} else if (getCode().equalsIgnoreCase("insert-now")) {
			setTTL(getTTL() - 1);
		} else if (getCode().equalsIgnoreCase("machineInsert -- mute for now.")) {
			setCode("ConnectRight");
			setSource(getMyIP());
			setKey("");
			setValue("");
			setTimestamp(System.currentTimeMillis());
			setTTL(1);
			setMsg_machine("loading-");
		} else if (getCode().equalsIgnoreCase("ConnectRight")) {

		} else {
			// dummy function to test.

		}

		isMessageDead(hashtable);

		return formMessageNow();
	}

	public void isMessageDead(Hashtable<Integer, String> hashtable) {
		if (getTTL() == 0) {
			if (getCode().equalsIgnoreCase("search")
					|| getCode().equalsIgnoreCase("delete")) {
				if (getDestination().equalsIgnoreCase("noDestination")) {
					message_print("(Not-" + getCode() + "ed)" + "Key|Value pair NOT found in DisMyRepo."
							+ "|Source was - " + getSource()
							+ "|This Machine is - " + getMyIP() + "|Key was ("
							+ getKey() + ")" + "|Latency is-"
							+ (System.currentTimeMillis() - getTimestamp())
							+ "|My Path was - " + getMsg_machine());
					
					String key_value_pair = "|Key-Value:" + getKey() + "-"
							+ getValue() + "|Source:" + getSource()
							+ "|Destination:" + getDestination() + "|Latency:"
							+ (System.currentTimeMillis() - getTimestamp());
					
					Append.appendData(RunSystem.MACHINE_NAME,
							key_value_pair);
					
				} else {
					message_print("Key|Value pair found in DisMyRepo at machine: "
							+ getDestination()
							+ " and key was "
							+ getCode()
							+ "ed"
							+ "|Pair is ("
							+ getKey()
							+ ","
							+ getValue()
							+ ")"
							+ "|Latency is-"
							+ (getTimestamp())
							+ "|My Path was - " + getMsg_machine());

					String key_value_pair = "|Key-Value:" + getKey() + "-"
							+ getValue() + "|Source:" + getSource()
							+ "|Destination:" + getDestination() + "|Latency:"
							+ (System.currentTimeMillis() - getTimestamp());
					
					Append.appendData(RunSystem.MACHINE_NAME,
							key_value_pair);
					
				}
				clearMessage();

			} else if (getCode().equalsIgnoreCase("insert")) {
				// message would be :loading-value1,value2,value3...
				String[] parsedMessage = getMsg_machine().split("-")[1]
						.split(",");
				int[] loadingPerMachine = new int[parsedMessage.length];
				int pos = 0;
				for (String string : parsedMessage) {
					loadingPerMachine[pos++] = Integer.parseInt(string);
					// System.out.print(string + ", ");
				}
				int positionMin = calculateMinPos(loadingPerMachine);
				if (positionMin == 0) {
					hashtable.put(Integer.parseInt(getKey()), getValue());
					message_print("Key|Value pair inserted in this machine. Pair is "
							+ getKey()
							+ "|"
							+ getValue()
							+ getMsg_machine()
							+ "|Source-"
							+ getSource()
							+ "|Destination-"
							+ getMyIP()
							+ "|Timestamp-"
							+ getTimestamp()
							+ "|TimeDifference-"
							+ (System.currentTimeMillis() - getTimestamp()));

					// System.out.println("TimeDifference, "
					// + (System.currentTimeMillis() - getTimestamp()));
					clearMessage();
				} else {
					setTTL(positionMin);
					setCode("insert-now");
					setDestination("");
					setMsg_machine("loading-");

					hashtable.put(Integer.parseInt(getKey()), getValue());
					message_print("Key|Value pair inserted in this machine. Pair is "
							+ getKey()
							+ "|"
							+ getValue()
							+ getMsg_machine()
							+ "|Source-"
							+ getSource()
							+ "|Destination-"
							+ getMyIP()
							+ "|Timestamp-"
							+ getTimestamp()
							+ "|TimeDifference-"
							+ (System.currentTimeMillis() - getTimestamp()));

					// System.out.println("TimeDifference, "
					// + (System.currentTimeMillis() - getTimestamp()));

					clearMessage();
				}
			} else if (getCode().equalsIgnoreCase("insert-now")) {
				hashtable.put(Integer.parseInt(getKey()), getValue());
				message_print("Key|Value pair inserted in this machine. Pair is "
						+ getKey()
						+ "|"
						+ getValue()
						+ getMsg_machine()
						+ "|Source-"
						+ getSource()
						+ "|Destination-"
						+ getMyIP()
						+ "|Timestamp-"
						+ getTimestamp()
						+ "|TimeDifference-"
						+ (System.currentTimeMillis() - getTimestamp()));

				// System.out.println("TimeDifference, "
				// + (System.currentTimeMillis() - getTimestamp()));

				clearMessage();

			}
		} else {
			// System.out.println("No message is NOT dead. It is passed on.");
		}
	}

	public static String getMyIP() {
		String myIPString = "";
		try {
			InetAddress ownIP = InetAddress.getLocalHost();
			myIPString = ownIP.getHostAddress();
			// System.out.println("IP of my system is := "+
			// ownIP.getHostAddress());
		} catch (Exception e) {
			System.out.println("Exception caught =" + e.getMessage());
		}
		return myIPString;
	}

	public void clearMessage() {
		setCode("");
		this.source = "";
		this.destination = "";
		setKey("");
		setValue("");
		setTimestamp(System.currentTimeMillis());
		setTTL(0);
		setMsg_machine("");
	}

	public String formMessageNow() {
		String message = "";
		message += getCode() + ":";
		message += getSource() + ":";
		message += getDestination() + ":";
		message += getKey() + ":";
		message += getValue() + ":";
		message += getTimestamp() + ":";
		message += getTTL() + ":";
		message += getMsg_machine() + ":";

		return message;
	}
}
